package com.xxl.job.admin.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.xxl.job.admin.config.PluginConstant;
import com.xxl.job.admin.core.model.PluginEntity;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.dao.PluginDao;
import com.xxl.job.admin.service.PluginService;
import com.xxl.job.admin.utils.ConvertUtils;
import com.xxl.job.admin.utils.FileUtils;
import com.xxl.job.admin.utils.ZipUtils;
import com.xxl.job.core.commons.base.PageData;
import com.xxl.job.core.commons.exception.DataxException;
import com.xxl.job.core.commons.mybatis.BaseServiceImpl;
import com.xxl.job.core.commons.response.DeleteResult;
import com.xxl.job.core.commons.response.ReturnCode;
import com.xxl.job.core.commons.vo.plugin.PluginAddVo;
import com.xxl.job.core.commons.vo.plugin.PluginQueryVo;
import com.xxl.job.core.commons.vo.plugin.PluginUpdateVo;
import com.xxl.job.core.commons.vo.plugin.PluginVo;
import com.xxl.job.core.config.ParameterConfig;
import com.xxl.job.core.constant.Constant;
import com.xxl.job.core.constant.DeleteStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * @author xuyue
 * @email xuyue@cetcbigdata.com
 * @date 2021-4-20 17:10
 * 解析器管理service实现类
 */
@Service
@Slf4j
@Transactional(rollbackFor = Exception.class)
public class PluginServiceImpl extends BaseServiceImpl<PluginDao, PluginEntity> implements PluginService {
    @Value("${datax.path.plugin}")
    private String PLUGIN_PATH;

    @Override
    public String add(PluginAddVo pluginAddVo) {
        //字段校验
        if (!nameNotExist(pluginAddVo.getName())) {
            DataxException.cast("解析器名称已存在，请重新输入", ReturnCode.INVALID_PARAMETER);
        }
        if (!fileNameNotExist(pluginAddVo.getFile().getOriginalFilename(), pluginAddVo.getType())) {
            DataxException.cast("解析器文件名称已存在，请重新输入", ReturnCode.INVALID_PARAMETER);
        }

        String uuid = UUID.randomUUID().toString();
        PluginEntity pluginEntity = ConvertUtils.sourceToTarget(pluginAddVo, PluginEntity.class);


        pluginEntity.setFileName(pluginAddVo.getFile().getOriginalFilename());
        pluginEntity.setFileVolumn(pluginAddVo.getFile().getSize() / 1024);
        pluginEntity.setUuid(uuid);
        pluginEntity.setDeleteStatus(DeleteStatus.NOT);
        pluginEntity.setCreateTime(new Date());
        pluginEntity.setUpdateTime(new Date());

        updatePluginFile(pluginAddVo.getFile(), pluginAddVo.getType());
        this.insert(pluginEntity);
        return uuid;
    }

    @Override
    public Boolean update(PluginUpdateVo pluginUpdateVo) {
        PluginEntity oldEntity = this.getEntityByUuid(pluginUpdateVo.getUuid());
        if (null == oldEntity) {
            DataxException.cast("拟更新解析器不存在", ReturnCode.INVALID_PARAMETER);
        }

        if (!pluginUpdateVo.getType().equals(oldEntity.getType())) {
            DataxException.cast("解析器类型不能修改", ReturnCode.INVALID_PARAMETER);
        }

        if (!pluginUpdateVo.getName().equals(oldEntity.getName())) {
            if (!nameNotExist(pluginUpdateVo.getName())) {
                DataxException.cast("解析器名称已存在，请重新输入", ReturnCode.INVALID_PARAMETER);
            } else {
                oldEntity.setName(pluginUpdateVo.getName());
            }
        }
        if (null == pluginUpdateVo.getFile()) {
            log.info("没有更新解析器文件");
        } else {
            if (!fileNameNotExist(pluginUpdateVo.getFile().getOriginalFilename(), pluginUpdateVo.getType())) {
                DataxException.cast("解析器文件名称已存在，请重新输入", ReturnCode.INVALID_PARAMETER);
            }
            oldEntity.setFileName(pluginUpdateVo.getFile().getOriginalFilename());
            oldEntity.setFileVolumn(pluginUpdateVo.getFile().getSize() / 1024);
            deleteFile(oldEntity.getFileName(), pluginUpdateVo.getType());
            updatePluginFile(pluginUpdateVo.getFile(), pluginUpdateVo.getType());
        }

        oldEntity.setUpdateTime(new Date());
        this.baseDao.updateById(oldEntity);
        return true;
    }

    @Override
    public List<DeleteResult> delete(List<String> uuids) {
        List<DeleteResult> list = new ArrayList<>();
        for (String item : uuids) {
            DeleteResult result = delete(item);
            if (null != result) {
                list.add(result);
            }
        }
        return list;
    }

    @Override
    public PluginVo query(String uuid) {
        if (StringUtils.isEmpty(uuid)) {
            DataxException.cast("查询UUID不能为空", ReturnCode.INVALID_PARAMETER);
        }
        PluginEntity entity = getEntityByUuid(uuid);
        if (null == entity) {
            DataxException.cast("查询解析器不存在或已被删除", ReturnCode.INVALID_PARAMETER);
        }
        return ConvertUtils.sourceToTarget(entity, PluginVo.class);
    }

    @Override
    public PageData<PluginVo> list(PluginQueryVo pluginQueryVo) {

        HashMap<String, Object> params = new HashMap<>();
        params.put(Constant.LIMIT, pluginQueryVo.getLimit());
        params.put(Constant.PAGE, pluginQueryVo.getPage());
        params.put(Constant.ORDER, pluginQueryVo.getOrder());
        params.put(Constant.ORDER_FIELD, pluginQueryVo.getOrderField());

        Wrapper<PluginEntity> wrapper = getWrapper(pluginQueryVo);

        IPage<PluginEntity> queryPage = getPage(params, Constant.CREATE_DATE, false);
        IPage<PluginEntity> page = baseDao.selectPage(queryPage, wrapper);
        return getPageData(page, PluginVo.class);
    }

    /**
     * 删除单个解析器
     */
    private DeleteResult delete(String uuid) {
        DeleteResult result = new DeleteResult();
        PluginEntity entity = getEntityByUuid(uuid);
        if (null == entity) {
            result.setMsg("拟删除解析器不存在：" + uuid);
            result.setUuid(uuid);
            return result;
        }
        try {
            this.deleteFile(entity.getFileName(), entity.getType());
        } catch (Exception e) {
            e.printStackTrace();
            result.setMsg("删除解析器文件时发生异常：" + entity.getName());
            result.setUuid(uuid);
            result.setName(entity.getName());
        }
        entity.setDeleteStatus(DeleteStatus.YES);
        this.baseDao.updateById(entity);
        return null;
    }

    @Override
    public void checkFile(MultipartFile file) throws DataxException {
        if (null == file) {
            DataxException.cast("解析器文件不能为空", ReturnCode.INVALID_PARAMETER);
        }
        if (file.isEmpty()) {
            DataxException.cast("解析器文件不能为空", ReturnCode.INVALID_PARAMETER);
        }
        if (!FileUtils.FileExt.ZIP.getExt().equals(FileUtils.getFileExt(file))) {
            DataxException.cast("解析器文件必须是压缩文件（zip）", ReturnCode.INVALID_PARAMETER);
        }
        if (file.getSize() > ParameterConfig.PLUGIN_FILE_MAX_B) {
            DataxException.cast("解析器文件不能超过" + ParameterConfig.PLUGIN_FILE_MAX_MB + "MB", ReturnCode.INVALID_PARAMETER);
        }
    }


    /**
     * 更新文件，如果已存在，则替换
     */
    private void updatePluginFile(MultipartFile orginFile, String type) {

        //获取解析器存储地址路径
        String filePath = getFilePath(type);
        //解析器存储地址根路径
        String fileParentPath = getFilePath(type);
        File folder = new File(fileParentPath);
        if (!folder.exists() && !folder.isDirectory()) {
            folder.mkdirs();
        }
        //解析器压缩文件路径
        filePath += orginFile.getOriginalFilename();
        //压缩后的文件
        String fileStorePath = filePath.replace("." + FileUtils.getFileExt(orginFile), "");

        //如果压缩后的文件已存在
        File tmpFile = new File(fileStorePath);
        if (tmpFile.exists()) {
            DataxException.cast("解析器已存在", ReturnCode.FILE_OPERATION_ERROR);
        }

        //文件压缩
        File file = new File(filePath);
        try {
            orginFile.transferTo(file);
            ZipUtils.unPacket(filePath, fileParentPath);
        } catch (IOException e) {
            DataxException.cast("存储解析器文件时发生异常：" + e.getMessage(), ReturnCode.FILE_OPERATION_ERROR);
        }
        //删除压缩文件
        file.delete();
    }

    /**
     * 删除对应的文件
     */
    private void deleteFile(String fileName, String type) {

        if (StringUtils.isEmpty(fileName)) {
            DataxException.cast("文件名称为空", ReturnCode.INVALID_PARAMETER);
        }

        //获取解析器存储地址路径
        String filePath = getFilePath(type);
        //解析器压缩文件路径
        filePath += fileName;

        String ext = "";
        int dot = fileName.lastIndexOf('.');
        if (dot > 0 && dot < fileName.length() - 1) {
            ext = fileName.substring(dot + 1).toLowerCase();
        }
        //压缩后的文件
        String fileStorePath = filePath.replace("." + ext, "");

        File file = new File(fileStorePath);
        FileUtils.deleteDirectory(file);
    }


    /**
     * 获取解析器存储路径
     */
    @Override
    public String getFilePath(String type) {
        StringBuffer filePath = new StringBuffer();
        if (StringUtils.isNotEmpty(PLUGIN_PATH)) {
            filePath.append(PLUGIN_PATH);
        } else {
            filePath.append(Thread.currentThread().getContextClassLoader().getResource("datax/plugin").getPath());
        }
        //拼接解析器文件路径-判断根路径是否/结尾
        if (!PLUGIN_PATH.endsWith(File.separator)) {
            filePath.append(File.separator);
        }
        //根据解析器类型存储到不同文件中（reader或writer）
        if (PluginConstant.PLUGIN_TYPE_READER.equals(type)) {
            filePath.append(PluginConstant.PLUGIN_TYPE_READER);
        } else if (PluginConstant.PLUGIN_TYPE_WRITER.equals(type)) {
            filePath.append(PluginConstant.PLUGIN_TYPE_WRITER);
        } else {
            DataxException.cast("解析器类型只能为reader或writer", ReturnCode.INVALID_PARAMETER);
        }
        filePath.append(File.separator);
        return filePath.toString();
    }

    @Override
    public List<XxlJobGroup> async() {
        return null;
    }

    /**
     * 校验名字
     */
    private boolean nameNotExist(String name) {
        QueryWrapper<PluginEntity> wrapper = getWrapper();
        wrapper.eq("name", name);
        List<PluginEntity> list = this.baseDao.selectList(wrapper);
        return null == list || list.isEmpty();
    }

    /**
     * 校验文件名字
     */
    private boolean fileNameNotExist(String fileName, String type) {
        QueryWrapper<PluginEntity> wrapper = getWrapper();
        wrapper.eq("file_name", fileName);
        wrapper.eq("type", type);
        List<PluginEntity> list = this.baseDao.selectList(wrapper);
        return null == list || list.isEmpty();
    }

    /**
     * 获得基本查询条件
     */
    private QueryWrapper getWrapper() {
        QueryWrapper<PluginEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("delete_status", DeleteStatus.NOT);
        return wrapper;
    }

    /**
     * 获得基本查询条件
     */
    private QueryWrapper getWrapper(PluginQueryVo pluginQueryVo) {
        QueryWrapper<PluginEntity> wrapper = new QueryWrapper<>();
        wrapper.eq("delete_status", DeleteStatus.NOT);
        if (null == pluginQueryVo) {
            log.info("没有查询条件");
        } else {
            if (StringUtils.isNotEmpty(pluginQueryVo.getName())) {
                wrapper.like("name", pluginQueryVo.getName());
            }
            if (StringUtils.isNotEmpty(pluginQueryVo.getFileName())) {
                wrapper.like("file_name", pluginQueryVo.getFileName());
            }
            if (StringUtils.isNotEmpty(pluginQueryVo.getType())) {
                wrapper.like("type", pluginQueryVo.getType());
            }
        }
        return wrapper;
    }

    /**
     * 根据UUID查询entity
     */
    private PluginEntity getEntityByUuid(String uuid) {
        if (StringUtils.isEmpty(uuid)) {
            log.warn("查询时UUID为空，查询失败");
            DataxException.cast("uuid为空，查询失败", ReturnCode.INVALID_PARAMETER);
        }
        QueryWrapper<PluginEntity> wrapper = getWrapper();
        wrapper.eq("uuid", uuid);
        List<PluginEntity> list = this.baseDao.selectList(wrapper);
        if (null == list || list.isEmpty()) {
            return null;
        } else if (list.size() > 1) {
            log.warn("存在重复的UUID");
        }
        return list.get(0);
    }

}
